package interpreter.builtin.functions;

import interpreter.Callable;

import java.math.BigInteger;
import java.util.List;

import math.ComplexNumber;
import math.exact.Complex;
import math.exact.Rational;

public class PlusFunction implements Callable {

	@Override
	public Object call(List<Object> arguments) {
		if (arguments.size() == 0)
			return new Complex(new Rational(BigInteger.ZERO));
		if (arguments.size() == 1)
			return arguments.get(0);
		makeSureArgumentsAreAllNumbers(arguments);
		if (hasInexactNumber(arguments))
			return computeInexactSum(arguments);
		return computeExactSum(arguments);
	}

	private void makeSureArgumentsAreAllNumbers(List<Object> arguments) {
		for (int i = 0; i < arguments.size(); i++)
			if (!(arguments.get(i) instanceof ComplexNumber))
				throw new InvalidArgumentType(i, arguments.get(i));
	}

	@SuppressWarnings("serial")
	public class InvalidArgumentType extends RuntimeException {
		private int argumentIndex;
		private Object argument;

		public InvalidArgumentType(int argumentIndex, Object argument) {
			super("argument " + (argumentIndex + 1) + " is not a number but a "
					+ argument.getClass().getName());
			this.argumentIndex = argumentIndex;
			this.argument = argument;
		}

		public int getArgumentIndex() {
			return argumentIndex;
		}

		public Object getArgument() {
			return argument;
		}
	}

	private boolean hasInexactNumber(List<Object> arguments) {
		for (Object arg : arguments)
			if (arg instanceof math.inexact.Complex)
				return true;
		return false;
	}

	private Object computeInexactSum(List<Object> arguments) {
		math.inexact.Complex result = inexact(arguments.get(0));
		for (int i = 1; i < arguments.size(); i++)
			result = result.add(((ComplexNumber) arguments.get(i)).toInexact());
		return result;
	}

	private math.inexact.Complex inexact(Object o) {
		return ((ComplexNumber) o).toInexact();
	}

	private Object computeExactSum(List<Object> arguments) {
		Complex result = (Complex) arguments.get(0);
		for (int i = 1; i < arguments.size(); i++)
			result = result.add((Complex) arguments.get(i));
		return result;
	}

}